什么是ROS 2包?
1.什么是ROS 2包?
包是ROS 2代码的组织单元。如果您希望能够安装代码或与他人共享代码,那么您需要将其组织成一个包。通过包,您可以发布ROS 2工作并允许其他人轻松构建和使用它。
ROS 2中的包创建使用ament作为构建系统和colcon作为构建工具。您可以使用CMake或Python创建一个包,这两种方式得到官方支持,尽管还存在其他构建类型。
2.什么构成了一个ROS 2软件包?
ROS 2的Python和CMake软件包各自有其最小要求的内容:
CMake
CMakeLists.txt文件,描述了如何构建软件包内的代码包含该包的公共头文件的
include/<package_name>目录包含有关该包的元信息的
package.xml文件包含该包源代码的
src目录
最简单的软件包的文件结构可能如下所示:
CMake
my_package/
CMakeLists.txt
include/my_package/
package.xml
src/Python
包含有关该包的元信息的
package.xml文件用于标记该包的
resource/<package_name>文件当一个软件包包含可执行文件时,需要使用
setup.cfg,以便ros2 run能够找到它们setup.py包含了安装该软件包的指令<package_name>- 与软件包同名的目录,被ROS 2工具用于查找软件包,包含__init__.py
最简单的软件包的文件结构可能如下所示:
Python
my_package/
package.xml
resource/my_package
setup.cfg
setup.py
my_package/3.工作空间中的3个软件包
一个工作空间可以包含任意多个软件包,每个软件包位于自己的文件夹中。您还可以在一个工作空间中拥有不同构建类型的软件包(如CMake、Python等)。但不能嵌套软件包。
最佳实践是在工作空间中创建一个src文件夹,并在其中创建您的软件包。这样可以保持工作空间的顶层“清洁”。
一个简单的工作空间可能如下所示:
workspace_folder/
src/
cpp_package_1/
CMakeLists.txt
include/cpp_package_1/
package.xml
src/
py_package_1/
package.xml
resource/py_package_1
setup.cfg
setup.py
py_package_1/
...
cpp_package_n/
CMakeLists.txt
include/cpp_package_n/
package.xml
src/4.创建一个包
首先, 配置您的 ROS 2 安装环境。
让我们使用您在 先前教程 中创建的工作空间 ros2_ws 来创建您的新软件包。
在运行软件包创建命令之前,请确保您位于 src 文件夹中。
cd ~/ros2_ws/src在ROS 2中创建新包的命令语法如下:
# python
ros2 pkg create --build-type ament_python <package_name>
# Cmake
ros2 pkg create --build-type ament_cmake <package_name>在本教程中,您将使用可选参数--node-name,它将在包中创建一个简单的Hello World类型的可执行文件。
在终端中输入以下命令:
# python
ros2 pkg create --build-type ament_python --node-name my_node my_package
#Cmake
ros2 pkg create --build-type ament_cmake --node-name my_node my_package现在,您的工作空间的src目录中将有一个名为my_package的新文件夹。
运行该命令后,您的终端会返回以下消息:
# python
going to create a new package
package name: my_package
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['<name> <email>']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: []
node_name: my_node
creating folder ./my_package
creating ./my_package/package.xml
creating source folder
creating folder ./my_package/my_package
creating ./my_package/setup.py
creating ./my_package/setup.cfg
creating folder ./my_package/resource
creating ./my_package/resource/my_package
creating ./my_package/my_package/__init__.py
creating folder ./my_package/test
creating ./my_package/test/test_copyright.py
creating ./my_package/test/test_flake8.py
creating ./my_package/test/test_pep257.py
creating ./my_package/my_package/my_node.py
# Cmake
going to create a new package
package name: my_package
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['<name> <email>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: []
node_name: my_node
creating folder ./my_package
creating ./my_package/package.xml
creating source and include folder
creating folder ./my_package/src
creating folder ./my_package/include/my_package
creating ./my_package/CMakeLists.txt
creating ./my_package/src/my_node.cpp您可以看到为新软件包自动生成的文件。
5.构建软件包
将软件包放入工作区尤其有价值,因为您可以通过在工作区根目录下运行colcon build一次性构建多个软件包。否则,您将需要逐个构建每个软件包。
返回工作区的根目录:
cd ~/ros2_ws现在你可以构建你的软件包:
colcon build从上个教程中你记得你的ros2_ws里也有ros_tutorials软件包。你可能已经注意到运行colcon build时还会构建turtlesim软件包。当你的工作区只有几个软件包时这没问题,但当有很多软件包时,colcon build会花费很长时间。
下次只构建my_package软件包,你可以运行:
colcon build --packages-select my_package6.导入设置文件
要使用您的新软件包和可执行文件,请首先打开一个新终端并导入您的主要ROS 2安装。
然后,在ros2_ws目录内运行以下命令以导入您的工作空间:
source install/local_setup.bash现在,您的工作空间已添加到路径中,您将能够使用您的新软件包的可执行文件。
7.使用软件包
要运行您使用--node-name参数在创建软件包时创建的可执行文件,请输入以下命令:
ros2 run my_package my_node将在终端返回一条消息:
# python
Hi from my_package.
# Cmake
hello world my_package package8.检查软件包内容
在 ros2_ws/src/my_package 内,您将看到 ros2 pkg create 自动创建的文件和文件夹:
# Python
my_package package.xml resource setup.cfg setup.py test
`my_node.py` 位于 `my_package` 目录中。这是您以后将放置所有自定义 Python 节点的位置。
# Cmake
CMakeLists.txt include package.xml src
`my_node.cpp` 位于 `src` 目录中。这是您以后将放置所有自定义 C++ 节点的位置。9.自定义 package.xml
略
10.总结
你创建了一个用于组织代码并方便他人使用的包。
您的软件包已自动填充所需文件,然后您使用 colcon 构建它,以便在本地环境中使用其可执行文件。
编写一个简单的发布者和订阅者(C++)
1.背景
节点 是在 ROS 图中进行通信的可执行进程。在本教程中,节点将以字符串消息的形式相互传递信息,通过一个 主题。这里使用的示例是一个简单的 "talker" 和 "listener" 系统;一个节点发布数据,另一个节点订阅该主题以接收数据。
这些示例中使用的代码可以在 这里 找到。
2.创建一个包
在一个新的终端中 初始化你的 ROS 2 安装,这样 ros2 命令才能正常工作。
进入在 上一个教程 中创建的 ros2_ws 目录。
记住,包应该创建在 src 目录中,而不是工作空间的根目录。所以,进入 ros2_ws/src 目录,并运行包创建命令:
ros2 pkg create --build-type ament_cmake cpp_pubsub终端会返回一条消息,确认已创建名为 cpp_pubsub 的包及其所有必要的文件和文件夹。
进入 ros2_ws/src/cpp_pubsub/src 目录。请注意,这是任何 CMake 包中包含可执行文件的源文件所在的目录。
wget -O publisher_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_publisher/member_function.cpp现在会有一个名为publisher_member_function.cpp的新文件。使用您喜欢的文本编辑器打开该文件。
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}2.1 检查代码
代码的开头包含了您将要使用的标准C++头文件。标准C++头文件之后是rclcpp/rclcpp.hpp的包含部分,它允许您使用ROS 2系统中最常见的组件。最后是std_msgs/msg/string.hpp,它包含了您将用于发布数据的内置消息类型。
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;这些行表示节点的依赖关系。请记住,依赖关系必须添加到package.xml和CMakeLists.txt中,在下一节中您将进行这些操作。
下一行通过从rclcpp::Node继承来创建节点类MinimalPublisher。代码中的每个this都是指向该节点。
class MinimalPublisher : public rclcpp::Node公共构造函数将节点命名为minimal_publisher,并将count_初始化为0。在构造函数内部,使用String消息类型、主题名称topic以及必要的队列大小来初始化发布者。接下来,初始化了timer_,它导致timer_callback函数每秒执行两次。
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}timer_callback函数是设置消息数据并实际发布消息的地方。RCLCPP_INFO宏确保每个发布的消息都会打印到控制台。
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}最后是定时器、发布者和计数器字段的声明。
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;下面是MinimalPublisher类的定义,接着是main函数,其中节点实际执行。rclcpp::init用于初始化ROS 2,rclcpp::spin则开始处理来自节点的数据,包括定时器的回调函数。
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}2.2 添加依赖项
返回到ros2_ws/src/cpp_pubsub目录,这是CMakeLists.txt和package.xml文件所在的目录。这些文件已经为您创建好。
用文本编辑器打开package.xml文件。
如在:doc:之前的教程 <./Creating-Your-First-ROS2-Package>`中提到的,确保填写``<description>`, <maintainer>和<license>标签:
<description>Examples of minimal publisher/subscriber using rclcpp</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>在ament_cmake构建工具依赖项之后添加一个新行,并粘贴以下与节点的包含语句对应的依赖项:
<depend>rclcpp</depend>
<depend>std_msgs</depend>这将在构建和执行代码时声明该包需要rclcpp和std_msgs.
确保保存文件.
2.3 CMakeLists.txt
现在打开 CMakeLists.txt 文件。在现有的依赖项 find_package(ament_cmake REQUIRED) 下方,添加以下行:
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)然后,添加可执行文件并将其命名为 talker,这样你就可以使用 ros2 run 运行你的节点:
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)最后,添加 install(TARGETS...) 部分,以便 ros2 run 可以找到你的可执行文件:
install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})你可以通过删除一些不必要的部分和注释来简化你的CMakeLists.txt,使其看起来像这样:
cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})
ament_package()现在您可以构建您的软件包,加载本地设置文件并运行它,但是让我们首先创建订阅者节点,这样您就可以看到整个系统的运行情况。
3.编写订阅者节点
返回到 ros2_ws/src/cpp_pubsub/src 目录,创建下一个节点。在终端中输入以下代码:
wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_subscriber/member_function.cpp现在在控制台输入 ls 将会返回:
publisher_member_function.cpp subscriber_member_function.cpp使用文本编辑器打开 subscriber_member_function.cpp。
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}
private:
void topic_callback(const std_msgs::msg::String & msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}3.1 检查代码
订阅者节点的代码与发布者几乎完全相同。现在节点被命名为 minimal_subscriber,构造函数使用节点的 create_subscription 类来执行回调函数。
没有定时器,因为订阅者只会在数据被发布到 topic 主题时做出响应。
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}回顾一下 主题教程 中提到,发布者和订阅者使用的主题名称和消息类型必须匹配,才能使它们进行通信。
topic_callback 函数接收通过主题发布的字符串消息数据,并使用 RCLCPP_INFO 宏将其简单地写入控制台。
这个类中仅有一个字段声明,即订阅。
private:
void topic_callback(const std_msgs::msg::String & msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;main 函数完全相同,只是现在它旋转 MinimalSubscriber 节点。对于发布者节点,旋转意味着启动计时器,而对于订阅者节点,它只是准备在消息到来时接收消息。
由于该节点与发布者节点具有相同的依赖关系,因此在package.xml中没有新内容可添加。
3.2 CMakeLists.txt
重新打开CMakeLists.txt,在发布者的条目下面添加订阅者节点的可执行文件和目标。
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)
install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})确保保存文件,然后您的发布/订阅系统就准备好了。
4.构建和运行
很可能您已经在您的ROS 2系统中安装了rclcpp和std_msgs软件包。在构建之前,最好在您的工作空间根目录(ros2_ws)中运行rosdep来检查是否存在缺失的依赖项:
rosdep install -i --from-path src --rosdistro humble -y仍然在您的工作空间根目录(ros2_ws)中,构建您的新软件包:
colcon build --packages-select cpp_pubsub打开一个新的终端,导航到 ros2_ws,并加载设置文件:
. install/setup.bash现在运行对话节点:
ros2 run cpp_pubsub talker终端应该会每0.5秒发布一条信息消息,如下所示:
[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [minimal_publisher]: Publishing: "Hello World: 4"打开另一个终端,在ros2_ws内部再次source设置文件,然后启动监听节点:
ros2 run cpp_pubsub listener监听器将从发布者当前的消息计数开始,将消息打印到控制台,就像这样:
[INFO] [minimal_subscriber]: I heard: "Hello World: 10"
[INFO] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [minimal_subscriber]: I heard: "Hello World: 12"
[INFO] [minimal_subscriber]: I heard: "Hello World: 13"
[INFO] [minimal_subscriber]: I heard: "Hello World: 14"在每个终端中输入Ctrl+C来停止节点的旋转。
5.总结
你创建了两个节点,用于在一个话题上发布和订阅数据。在编译和运行之前,你将它们的依赖项和可执行文件添加到了软件包配置文件中。
